﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using ICSharpCode.SharpZipLib.Zip;
using Infrastructure;
using Infrastructure.Logging;
using SubsDownloader.Core.AppSettings;
using SubsDownloader.Core.Entities;
using SubsDownloader.Core.EventArgs;
using SubsDownloader.Core.Interfaces;

namespace SubsDownloader.Core
{
    public abstract class SubtitlesProvider : ISubsDownloader
    {
        #region Private Members

        /// <summary>
        /// logger
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );

        private TitleFileInfo _titleFileInfo;
        readonly AutoResetEvent _autoResetEvent = new AutoResetEvent( false );
        private TitleResult _selectedTitle;
        private SubtitleResult _selectedSubtitle;

        #endregion

        public event EventHandler<SubsProgressEventArgs> ProgressChanged;

        public event EventHandler<NoMatchTitleFoundEventArgs> NoTitleMachFound;

        public event EventHandler<NoMatchSubtitleFoundEventArgs> NoSubtitleMachFound;

        #region ISubsDownloader Members

        public abstract string Name { get; }

        /// <summary>
        /// Download subtitles for given filename
        /// </summary>
        /// <param name="fileName">file to search subtitles for</param>
        public bool DownloadSubtitle ( string fileName )
        {
            var titleFileInfo = new TitleFileInfo( fileName );
            return DownloadSubtitle( titleFileInfo );
        }

        public bool DownloadSubtitle ( TitleFileInfo titleFileInfo )
        {
            _titleFileInfo = titleFileInfo;

            ProgressChanged += _titleFileInfo.ReportProgress;
            return StartDownloadProcess();
        }

        public void SelectTitle ( TitleResult result )
        {
            _selectedTitle = result;
            _autoResetEvent.Set();
        }

        public void CancelDownload ()
        {
            ReportProgress( 100, "Canceled by user" );
            _selectedTitle = null;
            _autoResetEvent.Set();
        }

        public void SelectSubtitles ( SubtitleResult subtitleResult )
        {
            _selectedSubtitle = subtitleResult;
            _autoResetEvent.Set();
        }

        #endregion

        protected abstract TitleResult[] FindTitles ( TitleFileInfo titleFileInfo );

        protected abstract SubtitleResult[] FindSubtitles ( TitleFileInfo titleFileInfo, TitleResult movieResult );

        #region Private Methods

        private bool StartDownloadProcess ()
        {
            //title
            ReportProgress( 10, "Searching title" );

            TitleResult[] movieResults = FindTitles( _titleFileInfo );
            if (movieResults == null || movieResults.Length == 0)
            {
                ReportProgress( 100, "Couldn't find title" );
                return false;
            }

            ReportProgress( 20, "Matching titles result" );
            TitleResult machedMovied = FindMatchedMovie( movieResults );
            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();

            if (machedMovied == null || subtitlesSettings.DownloadRisk == 0)
            {
                if (subtitlesSettings.DownloadRisk != 0)
                {
                    ReportProgress( 25, "No perfect match founded. waiting for the user to select" );
                }
                machedMovied = UserSelectTile( movieResults );

                if (machedMovied == null)
                {
                    ReportProgress( 100, "Couldnt find a match " );
                    return false;
                }
            }

            //subtitiles
            ReportProgress( 30, "Start Searching subtitles" );
            var subtitles = FindSubtitles( _titleFileInfo, machedMovied );
            if (subtitles == null || subtitles.Length == 0)
            {
                ReportProgress( 100, "Couldnt find subtitles" );
                return false;
            }

            ReportProgress( 40, "Matching titles result" );
            var matchedSubtitles = FindMatchedSubtitles( subtitles );
            if (matchedSubtitles == null || subtitlesSettings.DownloadRisk == 0)
            {
                if (subtitlesSettings.DownloadRisk != 0)
                {
                    ReportProgress( 45, "Searching matched subs" );
                    matchedSubtitles = RiskSelect( subtitles );
                }
                if (matchedSubtitles == null || subtitlesSettings.DownloadRisk == 0)
                {
                    ReportProgress( 50, "Wating for the user to select subtitles" );
                    matchedSubtitles = UserSelectSubtitles( subtitles );
                    if (matchedSubtitles == null)
                    {
                        ReportProgress( 100, "Couldnt match subtitles" );
                        return false;
                    }
                }
            }

            //download
            ReportProgress( 70, "downloading subtitles" );
            var success = DownloadSubtitleInternal( matchedSubtitles.Link );

            string msg = success ? "Subtitles successfully downloaded" : "Faild downloading subtitels";
            ReportProgress( 100, msg );
            _titleFileInfo.HasSubs = success;
            return success;
        }

        /// <summary>
        /// Find the title that mached the given media file info
        /// </summary>
        /// <param name="movieResults">movie result to search for subs</param>
        /// <returns>mached movie result</returns>
        private TitleResult FindMatchedMovie ( IEnumerable<TitleResult> movieResults )
        {
            TitleResult machedMovied = null;

            char[] invalidPathChars = Path.GetInvalidPathChars();

            foreach (TitleResult result in movieResults)
            {
                string movieName = invalidPathChars.Aggregate( result.Name, ( current, c ) => current.Replace( c, ' ' ) );

                movieName = movieName.Replace( ":", string.Empty );
                movieName = movieName.Replace( "'", string.Empty );

                if (_titleFileInfo.IsSeries != result.IsSeries || ( movieName.Trim().ToLower() != _titleFileInfo.TitleName.Trim().ToLower() ))
                    continue;

                if (!string.IsNullOrEmpty( _titleFileInfo.Year ) && !string.IsNullOrEmpty( result.Year ) && result.Year == _titleFileInfo.Year)
                {
                    machedMovied = result;
                    break;
                }

                if (string.IsNullOrEmpty( _titleFileInfo.Year ) || string.IsNullOrEmpty( result.Year ))
                {
                    machedMovied = result;
                    break;
                }
            }

            return machedMovied;
        }

        private TitleResult UserSelectTile ( IEnumerable<TitleResult> movieResults )
        {
            var noMatchFoundEventArgs = new NoMatchTitleFoundEventArgs( _titleFileInfo, movieResults.ToArray() );
            InvokeNoMovieMachFound( noMatchFoundEventArgs );
            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();

            int waitForUser = subtitlesSettings.WaitForUserToSelect;
            _autoResetEvent.WaitOne( waitForUser );

            TitleResult machedMovied = _selectedTitle;

            return machedMovied;
        }

        private SubtitleResult UserSelectSubtitles ( SubtitleResult[] subtitleResults )
        {
            InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, 60, string.Format( "Couldn't mach subtitle. please select." ) ) );

            var noMatchSubtitleFoundEventArgs = new NoMatchSubtitleFoundEventArgs( _titleFileInfo, subtitleResults );
            InvokeNoSubtitleMachFound( noMatchSubtitleFoundEventArgs );
            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();

            int waitForUser = subtitlesSettings.WaitForUserToSelect;

            _autoResetEvent.WaitOne( waitForUser );
            SubtitleResult subtitleResult = _selectedSubtitle;
            return subtitleResult;
        }

        private SubtitleResult FindMatchedSubtitles ( SubtitleResult[] subtitleResults )
        {
            SubtitleResult subtitleResult = null;

            if (subtitleResults == null || subtitleResults.Length == 0)
            {
                InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, 100, string.Format( "Couldn't find subtitles" ) ) );
                return subtitleResult;
            }

            foreach (SubtitleResult result in subtitleResults)
            {
                string subsName = result.Name.ToLower().Replace( '.', ' ' ).Trim();
                string titleName = _titleFileInfo.FileNameNoExt.ToLower().Replace( '.', ' ' ).Trim();
                string titleFolderName = _titleFileInfo.FolderName.ToLower().Replace( '.', ' ' ).Trim();

                if (subsName == titleName || subsName == titleFolderName)
                {
                    subtitleResult = result;
                    InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, 70, string.Format( "Perfect matched subtitle founded" ) ) );
                    break;
                }
            }

            return subtitleResult;
        }

        private SubtitleResult RiskSelect ( SubtitleResult[] subtitleResults )
        {
            SubtitleResult subtitleResult = null;
            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();
            var downloadRisk = subtitlesSettings.DownloadRisk;
            if (downloadRisk > 1)
            {
                //calculate subs priority
                foreach (SubtitleResult result in subtitleResults)
                {
                    //is hd
                    if (_titleFileInfo.IsHD && ( result.Name.ToLower().Contains( "1080p" ) || result.Name.ToLower().Contains( "720p" ) ))
                        result.Priority++;
                    //is hd with same version (1080P)
                    if (_titleFileInfo.IsHD && _titleFileInfo.FullHD.HasValue && _titleFileInfo.FullHD.Value && result.Name.ToLower().Contains( "1080p" ))
                        result.Priority++;
                    //is hd with same version (720P)
                    if (_titleFileInfo.IsHD && ( !_titleFileInfo.FullHD.HasValue || !_titleFileInfo.FullHD.Value ) && result.Name.ToLower().Contains( "720p" ))
                        result.Priority++;
                    //no hd
                    if (!_titleFileInfo.IsHD && !result.Name.ToLower().Contains( "1080p" ) && !result.Name.ToLower().Contains( "720p" ))
                        result.Priority++;

                    var subsMatches = Regex.Matches( _titleFileInfo.FileNameNoExt, @"\w[^/_/.\d{0,2}x{1}\d{0,2}]+", RegexOptions.IgnoreCase );
                    var titleMatches = Regex.Matches( result.Name, @"\w[^/_/.\d{0,2}x{1}\d{0,2}]+", RegexOptions.IgnoreCase );

                    foreach (Match match in subsMatches)
                    {
                        if (string.Equals( match.Value, _titleFileInfo.TitleName, StringComparison.CurrentCultureIgnoreCase ))
                            continue;

                        foreach (Match titleMatch in titleMatches)
                        {
                            if (string.Equals( match.Value, titleMatch.Value, StringComparison.CurrentCultureIgnoreCase ))
                            {
                                result.Priority++;
                            }
                        }
                    }


                    if (SameGroup( _titleFileInfo.FileNameNoExt, result.Name, '.' ) || SameGroup( _titleFileInfo.FolderName, result.Name, '.' ))
                        result.Priority++;
                    if (SameGroup( _titleFileInfo.FileNameNoExt, result.Name, ' ' ) || SameGroup( _titleFileInfo.FolderName, result.Name, ' ' ))
                        result.Priority++;
                    if (SameGroup( _titleFileInfo.FileNameNoExt, result.Name, '-' ) || SameGroup( _titleFileInfo.FolderName, result.Name, '-' ))
                        result.Priority++;
                    if (SameGroup( _titleFileInfo.FileNameNoExt, result.Name, '_' ) || SameGroup( _titleFileInfo.FolderName, result.Name, '_' ))
                        result.Priority++;
                }

                var tempSubtitleResults = new List<SubtitleResult>( subtitleResults );
                tempSubtitleResults.Sort();
                subtitleResults = tempSubtitleResults.ToArray();

                //find by the subs with highest priority
                if (( subtitleResults.Length > 1 && subtitleResults[0].Priority > subtitleResults[1].Priority ) ||
                  ( subtitleResults.Length == 1 && subtitleResults[0].Priority > 3 ))
                {
                    InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, 70, string.Format( "Matched subtitle founded using priority: {0}, download risk: {1}", subtitleResults[0].Name, 1 ) ) );
                    subtitleResult = subtitleResults[0];
                }

                if (downloadRisk > 2 && subtitleResult == null)
                {
                    InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, 70, string.Format( "Matched subtitle founded using priority: {0}, download risk: {1}", subtitleResults[0].Name, 2 ) ) );
                    subtitleResult = subtitleResults[0];
                }


                ////if there is subs result and is priority higher then 0 and there is only one. if there is only one result = true
                ////if there is more then one result and the first subs prioirty is higher then the second = true
                //if (subtitleResults[0].Priority > 0 && ( subtitleResults.Length == 1 || ( subtitleResults.Length > 1 && subtitleResults[0].Priority > subtitleResults[1].Priority ) ))
                //{
                //    InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, 70, string.Format( "Matched subtitle founded using priority: {0}", subtitleResults[0].Name ) ) );
                //    subtitleResult = subtitleResults[0];
                //}
            }
            return subtitleResult;
        }

        private string GetGroupName ( string text )
        {
            string groupName = string.Empty;
            var lastIndexOfDot = text.LastIndexOf( '.' );
            if (lastIndexOfDot > -1)
            {
                groupName = text.Substring( lastIndexOfDot, text.Length - lastIndexOfDot );
            }
            return groupName;
        }

        private bool DownloadSubtitleInternal ( string link )
        {
            var subtitlesSettings = SettingsManager.Instane.GetSettings<SubtitlesSettings>();
            string extractedSubsPath = Path.Combine( Path.GetTempPath(), "SubsDownloader", _titleFileInfo.ToString( "E" ) );
            string tempFileName = Path.Combine( extractedSubsPath, string.Format( "Subtitles_{0}.zip", Guid.NewGuid().ToString().Substring( 0, 5 ) ) );
            if (File.Exists( tempFileName ))
                File.Delete( tempFileName );

            if (!Directory.Exists( extractedSubsPath ))
            {
                Directory.CreateDirectory( extractedSubsPath );
            }
            var extractedSubsPathDirectoryInfo = new DirectoryInfo( extractedSubsPath );

            try
            {
                using (var webClient = new WebClient())
                {
                    webClient.DownloadFile( link, tempFileName );
                }
            }
            catch (Exception ex)
            {
                string msg = string.Format( "Faild downloading subtitels using link [{0}]", link );
                log.Error( msg, ex );
                return false;
            }


            try
            {
                var unzipper = new FastZip();
                unzipper.ExtractZip( tempFileName, extractedSubsPath, FastZip.Overwrite.Always, null, ".*", "", false );
            }
            catch (Exception ex)
            {
                string msg = "Faild to extract zip file";
                log.Error( msg, ex );
                return false;
            }

            foreach (var subtitlesType in subtitlesSettings.SubtitlesTypes)
            {
                foreach (FileInfo file in extractedSubsPathDirectoryInfo.GetFiles( string.Format( "*{0}", subtitlesType ) ))
                {
                    string newFileName = Path.Combine( _titleFileInfo.FolderPath, string.Format( "{0}{1}", _titleFileInfo.FileNameNoExt, subtitlesType ) );
                    int counter = 0;
                    while (File.Exists( newFileName ))
                    {
                        File.Move( newFileName, newFileName + "." + counter + ".old" );
                    }
                    File.Move( file.FullName, newFileName );
                }
            }

            if (File.Exists( tempFileName ))
                File.Delete( tempFileName );

            return true;

        }

        private bool SameGroup ( string fileNameNoExt, string subsName, char seperator )
        {
            int index = fileNameNoExt.LastIndexOf( seperator );
            if (index < 0)
                return false;

            string titleGroupName = fileNameNoExt.Substring( index );
            index = subsName.LastIndexOf( seperator );
            if (index < 0)
                return false;

            string subsGroupName = subsName.Substring( index );
            return titleGroupName.ToLower() == subsGroupName.ToLower();
        }

        private void ReportProgress ( int precentage, string msg )
        {
            InvokeProgressChanged( new SubsProgressEventArgs( _titleFileInfo, precentage, string.Format( "[{0}] {1}", Name, msg ) ) );
        }

        #endregion

        #region Event Handlers

        protected void InvokeProgressChanged ( SubsProgressEventArgs e )
        {
            log.Info( e.UserState );
            EventHandler<SubsProgressEventArgs> handler = ProgressChanged;
            if (handler != null)
                handler( this, e );
        }

        protected void InvokeNoMovieMachFound ( NoMatchTitleFoundEventArgs e )
        {
            EventHandler<NoMatchTitleFoundEventArgs> handler = NoTitleMachFound;
            if (handler != null)
                handler( this, e );
        }

        protected void InvokeNoSubtitleMachFound ( NoMatchSubtitleFoundEventArgs e )
        {
            EventHandler<NoMatchSubtitleFoundEventArgs> handler = NoSubtitleMachFound;
            if (handler != null)
                handler( this, e );
        }

        #endregion
    }
}